"""
Module to support Bank Operations using TCP/IP.

This handles incoming requests from TCP/IP, put in the request_queue and waits
for completion, then reply to client.

@author: U{Gustavo Sverzut Barbieri <barbieri@gmail.com>}
@license: GNU GPL
@copyright: (C) 2005 Gustavo Sverzut Barbieri
@version: $Rev$
"""

import socket
import threading

from threading import Thread
from passivereplication.coordinator import RequestQueue, ClientRequest
from SocketServer import ThreadingTCPServer, StreamRequestHandler

from debug import debug



class ClientServer( ThreadingTCPServer ):
    """Client Server: handle requests from clients.

    This TCP server uses threads to handle incoming requests from clients,
    it get requests and put in a L{RequestQueue}, which is shared with the
    L{Coordinator} thread.

    Client L{Requests} are queued and then the thread waits for it to be
    completed, returning its value.
    """

    allow_reuse_address = True
    def __init__( self, server_address, request_queue ):
        """Create a new ClientServer instance.

        @param server_address: a tuple C{( address, port )} to listen, ie:
                               C{( '', 8000 )}
        @type  server_address: L{tuple}

        @param request_queue: the queue to put requests, should be shared with
                              who will process requests.
        @type  request_queue: L{RequestQueue}
        """
        if not isinstance( request_queue, RequestQueue ):
            raise TypeError( "request_queue must be an RequestQueue instance.")

        self.request_queue = request_queue
        self.run           = False
        self.thread        = Thread( target=self.serve_forever )
        self.thread.setName( "Client Server" )

        ThreadingTCPServer.__init__( self, server_address,
                                     ClientServer.ClientRequestHandler )
    # __init__()


    def start( self ):
        """Start server.

        @note: This launches a new thread, thus is non-blocking.
        """
        debug( 1, "Server %s started. Listening to %s:%s" % \
               ( self.__class__.__name__, self.server_address[ 0 ],
                 self.server_address[ 1 ] ) )
        self.run = True
        self.thread.start()
    # start()


    def stop( self ):
        """Stop server."""
        self.run = False
        while self.thread.isAlive():
            debug( 2, "Thread %s still alive..." % self.thread.getName() )
            self.thread.join( 0.1 )

        self.server_close()
        debug( 1, "Server %s stopped." % self.__class__.__name__ )
    # stop()


    def serve_forever( self ):
        """Handle requests."""
        while self.run:
            self.handle_request()
        debug( 1, "User required %s to stop." % self.__class__.__name__ )
    # serve_forever()


    def get_request( self ):
        # Reimplemented to avoid blocking forever
        pto = self.socket.gettimeout()
        self.socket.settimeout( 1 )
        try:
            r = self.socket.accept()
            self.socket.settimeout( pto )
            return r
        except socket.timeout:
            self.socket.settimeout( pto )
            raise socket.error( "No connections within timeout, " \
                                "try again." )
    # get_request()


    def process_request( self, request, client_address ):
        # Overload to change thread's name
        self.process_request_lock.acquire()
        self.process_request_number += 1
        tid = self.process_request_number
        self.process_request_lock.release()

        t = threading.Thread( target = self.process_request_thread,
                              args = (request, client_address),
                              name = "Client-%d" % tid )
        if self.daemon_threads:
            t.setDaemon( 1 )

        t.start()
    # process_request()
    process_request_number = 0
    process_request_lock   = threading.RLock()




    class ClientRequestHandler( StreamRequestHandler ):
        """Request handler for incoming client connections.

        This will handle incoming connections, handle commands, put them in
        the request queue and then reply to other peer.

        Methods that begin with 'do_' are to handle client commands.
        """


        def setup( self ):
            StreamRequestHandler.setup( self )
            self._debug( 1, "Got connection." )
        # setup()


        def finish( self ):
            StreamRequestHandler.finish( self )
            self._debug( 1, "End connection." )
        # finish()


        def send_ok( self, reply=None ):
            self.wfile.write( "+OK\n\n" )
            if reply is not None:
                self.wfile.write( reply )
                self.wfile.write( "\n\n" )
            self.wfile.flush()
        # send_ok()


        def send_not_ok( self, reply=None ):
            self.wfile.write( "+NOT OK\n\n" )
            if reply is not None:
                self.wfile.write( reply )
                self.wfile.write( "\n\n" )
            self.wfile.flush()
        # send_not_ok()


        def handle( self ):
            actions = {
                "deposit":   self.do_bank_deposit,
                "withdraw":  self.do_bank_withdraw,
                "consult":   self.do_bank_consult,
                "transfer":  self.do_bank_transfer,
                "mconsult":  self.do_bank_mconsult,
                "mtransfer": self.do_bank_mtransfer,
                "quit":      self.do_quit,
                "dump":      self.do_dump,
                "help":      self.do_help,
                }
            run = True


            while run and self.server.run:
                line = self.rfile.readline()
                if not line:
                    self._debug( 2, "Empty line from peer, close connection." )
                    run = False
                    break

                line = line.strip()
                if not line:
                    continue # empty line
                for cmd, func in actions.iteritems():
                    if line.startswith( cmd ):
                        args = line[ len( cmd ) : ].split()
                        try:
                            v = func( args )
                            self.send_ok( v )

                        except StopIteration:
                            run = False

                        except SyntaxError, e:
                            self.send_not_ok( ( "Wrong Syntax! " \
                                                "Correct usage is: %s" ) \
                                              % e )
                        except socket.error:
                            run = False

                        except Exception, e:
                            self.send_not_ok( "ERROR: %s: %s" % \
                                              ( e.__class__.__name__, e ) )

                            debug( 0,
                                   """\
------------------------------------------------------------
Begin Traceback for Client: %(client_address)s:%(client_port)s

%(traceback)s

------------------------------------------------------------
""" % { "client_address": self.client_address[ 0 ],
        "client_port": self.client_address[ 1 ],
        "traceback": traceback.format_exc() }
                                   )
                        # end try - except
                        break
                else: # if command not recognized
                    self.send_not_ok( "Command not recognized: %s" % line )
                # end for
        # handle()


        def exec_client_command( self, command, rid, args ):
            """Execute client commands.

            This creates a new L{Request} and put it in the L{RequestQueue}
            that coordinator will listen and process requests.

            @param command: command to be issued.
            @param rid: request identifier.
            @param args: arguments to command.

            @return: result of this request.

            @note: this call blocks until request is processed.
            """
            rid = "%s:%s" % ( self.client_address[ 0 ],  rid )
            request = ClientRequest( rid, command, args )
            self.server.request_queue.put( request )
            return request.wait()
        # exec_client_command()


        def do_bank_deposit( self, args ):
            if len( args ) < 3:
                raise SyntaxError( "deposit <rid> <account> <value>" )

            acc = int( args[ 1 ] )
            val = int( args[ 2 ] )

            self.exec_client_command( 'deposit', args[ 0 ], ( acc, val ) )
        # do_bank_deposit()


        def do_bank_withdraw( self, args ):
            if len( args ) < 3:
                raise SyntaxError( "withdraw <rid> <account> <value>" )

            acc = int( args[ 1 ] )
            val = int( args[ 2 ] )

            self.exec_client_command( 'withdraw', args[ 0 ], ( acc, val ) )
        # do_bank_withdraw()


        def do_bank_consult( self, args ):
            if len( args ) < 2:
                raise SyntaxError( "consult <rid> <account>" )

            acc = int( args[ 1 ] )

            v = self.exec_client_command( 'consult', args[ 0 ], ( acc, ) )
            return str( v )
        # do_bank_consult()


        def do_bank_mconsult( self, args ):
            if len( args ) < 2:
                raise SyntaxError( "mconsult <rid> <acc1> ... <accN>" )

            accounts = [ int( a ) for a in args[ 1: ] ]
            vs = self.exec_client_command( 'multipleConsult', args[ 0 ],
                                           ( accounts , ) )
            reply = " ".join( [ str( v ) for v in vs ] )
            return reply
        # do_bank_mconsult()


        def do_bank_transfer( self, args ):
            if len( args ) < 4:
                raise SyntaxError( "transfer <rid> <src> <dst> <value>" )

            src = int( args[ 1 ] )
            dst = int( args[ 2 ] )
            val = int( args[ 3 ] )

            self.exec_client_command( 'transfer', args[ 0 ],
                                      ( src, dst, val ) )
        # do_bank_transfer()


        def do_bank_mtransfer( self, args ):
            rid  = args[ 0 ]
            args = args[ 1: ]
            line = " ".join( args )
            try:
                srcs, dsts, values = line.split( "," )
            except:
                raise SyntaxError( "mtransfer <rid> <src1> ... <srcN>, " \
                                   "<dst1> ... <dstN>, <val1> ... <valN>" )

            srcs = [ int( s ) for s in srcs.split() ]
            dsts = [ int( d ) for d in dsts.split() ]

            values = [ int( v ) for v in values.split() ]

            self.exec_client_command( 'multipleTransfer', rid,
                                      ( srcs, dsts, values ) )
        # do_bank_mtransfer()


        def do_dump( self, args ):
            return self.exec_client_command( 'dump', None, () )
        # do_dump()


        def do_quit( self, args ):
            raise StopIteration
        # do_quit()


        def do_help( self, args ):
            return """\
deposit <rid> <account> <value>         deposits value to account
withdraw <rid> <account> <value>        withdraws value from account
consult <rid> <account>                 consults account status
transfer <rid> <src> <dst> <value>      transfers value from src account to dst
mconsult <rid> <acc1> <acc2> ...        consult multiple accounts
mtransfer <rid> <srcs>, <dsts>, <vals>  do multiple transfers
dump                                    dump status to file
help                                    this message
quit                                    exit bank
.
where <rid> is the request id, multiple operations with same request id are
not performed twice.
    """
        # do_help()


        def _debug( self, level, message ):
            """Print message to standard error based on current debug level and
            message level.

            Message is only printed if C{level >= debug.level}.

            @param level: message level
            @type  level: L{int}
            @param message: what to print.
            """
            debug( level, "%s: %s:%s: %s" % \
                   ( self.__class__.__name__,
                     self.client_address[ 0 ],
                     self.client_address[ 1 ],
                     message ) )
        # _debug()
    # ClientRequestHandler
# ClientServer
